C++ 基础
从 hello world 到通讯录管理系统:变量、数据类型、运算符、流程结构、数组、函数、指针、结构,一路把 C 语言底子搬到 C++。
hello C++
#include <iostream>
using namespace std;
int main()
{
cout << "hello C++" << endl;
system("pause");
return 0;
}
C++ 中以流的形式进行输入输出,而不再是 printf 和 scanf;头文件也要修改成 iostream,不需要 .h。
那 using namespace std; 是什么含义???
endl 可以省略,作用为换行。
变量 常量 命名
变量
作用:给一段指定的内存空间起名,方便操作这段内存。
语法:数据类型 变量名 = 初始值;
#include <iostream>
using namespace std;
int main() {
// 变量的定义
// 语法:数据类型 变量名 = 初始值
int a = 10;
cout << "a = " << a << endl;
system("pause");
return 0;
}
注意:C++ 在创建变量时,必须给变量一个初始值,否则会报错。
常量
作用:用于记录程序中不可更改的数据。
两种方式:
#define 宏常量
#define 常量名 常量值
通常在文件上方定义,表示一个常量。
const 修饰的变量
语法:const 数据类型 常量名 = 常量值
通常在变量定义前加关键字 const,修饰该变量为常量,不可修改。
#include <iostream>
using namespace std;
// 1、宏常量
#define day 7
int main() {
cout << "一周里总共有 " << day << " 天" << endl;
// day = 8; // 报错,宏常量不可以修改
// 2、const 修饰变量
const int month = 12;
cout << "一年里总共有 " << month << " 个月份" << endl;
// month = 24; // 报错,常量是不可以修改的
system("pause");
return 0;
}
关键字
关键字是 C++ 中预先保留的单词(标识符)。
标识符命名规则
作用:C++ 规定给标识符(变量、常量)命名时,有一套自己的规则。
(1)标识符不能是关键字
(2)标识符只能由字母、数字、下划线组成
(3)第一个字符必须为字母或下划线
(4)标识符中字母区分大小写
给标识符命名时,争取做到见名知意的效果,方便自己和他人的阅读。
数据类型
C++ 规定在创建一个变量或者常量时,必须要指定出相应的数据类型,否则无法给变量分配内存。
整型
作用:整型变量表示的是整数类型的数据。
C++ 中能够表示整型的类型有以下几种方式,区别在于所占内存空间不同:
实型(浮点型)
作用:用于表示小数。
浮点型变量分为两种:单精度 float、双精度 double。两者的区别在于表示的有效数字范围不同。
使用 float a = 3.14; 会进行转换,因为计算机看到 3.14 就认为是 double——实际上这条语句还做了一次把 double 3.14 转换成 float 3.14 的操作。所以在使用 float 的时候可以采用 float a = 3.14f;,告诉计算机这是单精度。
使用 cout 输出时默认只会保留六位有效数字。
字符型
作用:字符型变量用于显示单个字符。
语法:char ch = 'a';
注意 1:在显示字符型变量时,用单引号将字符括起来,不要用双引号。
注意 2:单引号内只能有一个字符,不可以是字符串。
C 和 C++ 中字符型变量只占用 1 个字节。字符型变量并不是把字符本身放到内存中存储,而是将对应的 ASCII 编码放入到存储单元。
ASCII 码大致由以下两部分组成:
- ASCII 非打印控制字符:ASCII 表上的数字 0-31 分配给了控制字符,用于控制像打印机等一些外围设备。
- ASCII 打印字符:数字 32-126 分配给了能在键盘上找到的字符,当查看或打印文档时就会出现。
tips:按住 alt 再按一个数字,就可以打出它的 ASCII 码对应的字符。
对于一些不能显示出来的 ASCII 字符:
要用到转义字符。
sizeof 关键字
作用:利用 sizeof 关键字可以统计数据类型所占内存大小。
语法:sizeof(数据类型 / 变量)
int a = 3;
sizeof(int);
sizeof(a);
字符串
作用:用于表示一串字符。
两种风格:
C 风格字符串:char 变量名[] = "字符串值"
char str1[] = "hello world";
cout << str1 << endl;
C++ 风格字符串:需要加入头文件 #include <string>,语法:string 变量名 = "字符串值"
string str = "hello world";
cout << str << endl;
在 "" 里的就是字符串,直接出现在 cout 中也是一样。字符串存放在一个比较高的地址。所谓字符串类型也不过就是把这串字母做一个打包、给它一个名字,以实现再次使用时无需重新打出一整串,而只要用一个 str1 或者 str 就行。
bool 类型
作用:布尔数据类型代表真或假的值。
bool 类型只有两个值:
- true --- 真(本质是 1)
- false --- 假(本质是 0)
bool 类型占1 个字节大小。
输入输出
作用:用于从键盘获取数据。
关键字:cin。语法:cin >> 变量
// 整型输入
int a;
cout << "请输入整型变量:";
cin >> a;
cout << a << endl;
// 浮点型输入
double d;
cout << "请输入浮点型变量:";
cin >> d;
cout << d << endl;
// 字符型输入
char ch;
cout << "请输入字符型变量:";
cin >> ch;
cout << ch << endl;
// 字符串型输入
string str;
cout << "请输入字符串型变量:";
cin >> str;
cout << str << endl;
// 布尔类型输入
bool flag;
cout << "请输入布尔型变量:";
cin >> flag;
cout << flag << endl;
运算符
作用:用于执行代码的运算。
| 运算符类型 | 作用 |
|---|---|
| 算术运算符 | 用于处理四则运算 |
| 赋值运算符 | 用于将表达式的值赋给变量 |
| 比较运算符 | 用于表达式的比较,并返回一个真值或假值 |
| 逻辑运算符 | 用于根据表达式的值返回真值或假值 |
算术运算符
作用:用于处理四则运算。
算术运算符包括以下符号:
| 运算符 | 术语 | 示例 | 结果 |
|---|---|---|---|
| + | 正号 | +3 | 3 |
| - | 负号 | -3 | -3 |
| + | 加 | 10 + 5 | 15 |
| - | 减 | 10 - 5 | 5 |
| * | 乘 | 10 * 5 | 50 |
| / | 除 | 10 / 5 | 2 |
| % | 取模(取余) | 10 % 3 | 1 |
| ++ | 前置递增 | a=2; b=++a; | a=3; b=3; |
| ++ | 后置递增 | a=2; b=a++; | a=3; b=2; |
| -- | 前置递减 | a=2; b=--a; | a=1; b=1; |
| -- | 后置递减 | a=2; b=a--; | a=1; b=2; |
注意:
- 在除法运算中,除数不能为 0
- 只有整型变量可以进行取模运算
- 前置递增先对变量进行 ++,再计算表达式,后置递增相反
赋值运算符
作用:用于将表达式的值赋给变量。
赋值运算符包括以下几个符号:
| 运算符 | 术语 | 示例 | 结果 |
|---|---|---|---|
| = | 赋值 | a=2; b=3; | a=2; b=3; |
| += | 加等于 | a=0; a+=2; | a=2; |
| -= | 减等于 | a=5; a-=3; | a=2; |
| *= | 乘等于 | a=2; a*=2; | a=4; |
| /= | 除等于 | a=4; a/=2; | a=2; |
| %= | 模等于 | a=3; a%=2; | a=1; |
比较运算符
作用:用于表达式的比较,并返回一个真值或假值。
比较运算符有以下符号:
| 运算符 | 术语 | 示例 | 结果 |
|---|---|---|---|
| 相等于 | 4 3 | 0 | |
| != | 不等于 | 4 != 3 | 1 |
| < | 小于 | 4 < 3 | 0 |
| > | 大于 | 4 > 3 | 1 |
| <= | 小于等于 | 4 <= 3 | 0 |
| >= | 大于等于 | 4 >= 1 | 1 |
注意:C 和 C++ 语言的比较运算中,"真"用数字 "1" 来表示,"假"用数字 "0" 来表示。
逻辑运算符
作用:用于根据表达式的值返回真值或假值。
逻辑运算符有以下符号:
| 运算符 | 术语 | 示例 | 结果 |
|---|---|---|---|
| ! | 非 | !a | 如果 a 为假,则 !a 为真;如果 a 为真,则 !a 为假。 |
| && | 与 | a && b | 如果 a 和 b 都为真,则结果为真,否则为假。 |
| || | 或 | a || b | 如果 a 和 b 有一个为真,则结果为真,二者都为假时,结果为假。 |
非:真变假,假变真。
与:同真为真,其余为假。
或:同假为假,其余为真。
程序流程结构
C/C++ 支持最基本的三种程序运行结构:顺序结构、选择结构、循环结构。
- 顺序结构:程序按顺序执行,不发生跳转
- 选择结构:依据条件是否满足,有选择的执行相应功能
- 循环结构:依据条件是否满足,循环多次执行某段代码
选择结构
if 语句
作用:执行满足条件的语句。
if 语句的三种形式:单行格式 if 语句、多行格式 if 语句、多条件的 if 语句。
单行格式 if 语句:
if (条件)
{
条件满足执行的语句;
}
多行格式 if 语句:
if (条件)
{
条件满足执行的语句;
}
else
{
条件不满足执行的语句;
}
多条件的 if 语句:
if (条件1)
{
条件1满足执行的语句;
}
else if (条件2)
{
条件2满足执行的语句;
}
...
else
{
都不满足执行的语句;
}
嵌套 if 语句:在 if 语句中,可以嵌套使用 if 语句,达到更精确的条件判断。
三目运算符
作用:通过三目运算符实现简单的判断。
语法:表达式1 ? 表达式2 : 表达式3
- 如果表达式 1 的值为真,执行表达式 2,并返回表达式 2 的结果
- 如果表达式 1 的值为假,执行表达式 3,并返回表达式 3 的结果
返回的是变量,可以继续当成左值被赋值:
int a = 10;
int b = 20;
(a > b ? a : b) = 100;
相当于给 b 赋值 100。和 if 语句比较,三目运算符优点是短小整洁,缺点是如果用嵌套,结构不清晰。
switch 语句
作用:执行多条件分支语句。
switch(表达式)
{
case 结果1: 执行语句; break;
case 结果2: 执行语句; break;
...
default: 执行语句; break;
}
注意:
- switch 语句中表达式类型只能是整型或者字符型
- case 里如果没有 break,那么程序会一直向下执行
- 与 if 语句比,对于多条件判断时,switch 的结构清晰,执行效率高;缺点是 switch 不可以判断区间
循环结构
while
作用:满足循环条件,执行循环语句。
while (循环条件)
{
循环语句;
}
只要循环条件的结果为真,就执行循环语句。在执行循环语句时候,程序必须提供跳出循环的出口,否则出现死循环。
do...while
作用:满足循环条件,执行循环语句。
do
{
循环语句;
} while (循环条件);
与 while 的区别在于 do...while 会先执行一次循环语句,再判断循环条件。
for
作用:满足循环条件,执行循环语句。
for (起始表达式; 条件表达式; 末尾循环体)
{
循环语句;
}
for 循环中的表达式,要用分号进行分隔。三者都是常用的循环语句,for 循环结构比较清晰,用的较多。
跳转语句
break 语句
作用:用于跳出选择结构、循环结构。
break 使用的时机:
- 出现在 switch 条件语句中,作用是终止 case 并跳出 switch
- 出现在循环语句中,作用是跳出当前的循环语句
- 出现在嵌套循环中,跳出最近的内层循环语句
continue 语句
作用:在循环语句中,跳过本次循环中余下尚未执行的语句,继续执行下一次循环。
continue 并没有使整个循环终止,而 break 会跳出循环。找奇数:
for (int i = 0; i < 100; i++)
{
if (i % 2 == 0)
continue;
cout << i << endl;
}
goto 语句
作用:可以无条件跳转语句。
语法:goto 标记;
解释:如果标记的名称存在,执行到 goto 语句时,会跳转到标记的位置。在程序中不建议使用 goto 语句,以免造成程序流程混乱。
练习:C++ 猜数
#include <iostream>
#include <ctime>
using namespace std;
int main()
{
srand((unsigned int)time(NULL));
int num = rand() % 100 + 1;
int val;
while (1) {
cin >> val;
if (val > num) {
cout << "大了" << endl;
}
else if (val < num) {
cout << "小了" << endl;
}
else {
cout << "猜对了" << endl;
break;
}
}
system("pause");
return 0;
}
仍然是用系统时间作为随机数种子,不一样的地方在于头文件变成了 ctime。
敲桌子
输出 1~100 中所有不含 7 的数(7 的倍数或含数字 7 的"敲桌子"):
#include <iostream>
using namespace std;
int main()
{
for (int i = 1; i <= 100; i++)
{
if (i % 7 == 0 || i % 10 == 7 || i / 10 == 7)
cout << "敲桌子" << endl;
else
cout << i << endl;
}
system("pause");
return 0;
}
九九乘法表
#include <iostream>
using namespace std;
int main()
{
for (int i = 1; i < 10; i++)
{
for (int j = 1; j <= i; j++)
cout << j << "*" << i << "=" << j * i << "\t";
cout << endl;
}
system("pause");
return 0;
}
cout << endl; 相当于 printf("\n");。cout 采用的就不再是之前填空的方式了,貌似更直接了当:输出什么、再输出什么。
数组
所谓数组,就是一个集合,里面存放了相同类型的数据元素。
- 特点 1:数组中的每个数据元素都是相同的数据类型
- 特点 2:数组是由连续的内存位置组成的
一维数组
一维数组定义的三种方式:
数据类型 数组名[数组长度];
数据类型 数组名[数组长度] = { 值1, 值2 ... };
数据类型 数组名[] = { 值1, 值2 ... };
注意:数组名的命名规范与变量名命名规范一致,不要和变量重名;数组中下标是从 0 开始索引。
数组名的用途:
- 统计整个数组在内存中的长度:
cout << "整个数组所占内存空间为: " << sizeof(arr) << endl;
- 获取数组在内存中的首地址:
cout << "数组首地址为: " << (int)arr << endl;
在 C++ 中无需使用 %d、%p 等表示输出的格式。此外还有一些小技巧:
cout << "每个元素所占内存空间为: " << sizeof(arr[0]) << endl;
cout << "数组的元素个数为: " << sizeof(arr) / sizeof(arr[0]) << endl;
通过下标找地址,可以发现每个单元间都隔着一个类型的长度:
cout << "数组中第一个元素地址为: " << (int)&arr[0] << endl; // 与 arr 的地址一样
cout << "数组中第二个元素地址为: " << (int)&arr[1] << endl;
总结:
- 直接打印数组名,可以查看数组所占内存的首地址
- 对数组名进行 sizeof,可以获取整个数组占内存空间的大小
- 数组名是常量,不可以作为左值被赋值(const 指针)
示例
找最大数:
int arr[] = { 20,40,27,55,36 };
int max = arr[0];
for (int i = 0; i + 1 < sizeof(arr) / sizeof(arr[0]); i++)
{
int x = arr[i] > arr[i + 1] ? arr[i] : arr[i + 1]; // 相邻两个取大
max = x > max ? x : max;
}
cout << max << endl;
数组逆序:
int arr[] = { 1,2,3,4,5 };
int first = 0;
int last = sizeof(arr) / sizeof(arr[0]) - 1;
int n;
for (; first < last; first++, last--)
{
n = arr[first];
arr[first] = arr[last];
arr[last] = n;
}
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
cout << arr[i];
冒泡排序:
int arr[9] = { 4,2,8,0,5,7,1,3,9 };
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]) - 1; i++)
{
for (int j = 0; j < sizeof(arr) / sizeof(arr[0]) - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
cout << arr[i] << endl;
单步运行观察 temp 的交换过程:
二维数组
二维数组就是在一维数组上,多加一个维度。
四种定义方式:
数据类型 数组名[行数][列数];
数据类型 数组名[行数][列数] = { {数据1, 数据2}, {数据3, 数据4} };
数据类型 数组名[行数][列数] = { 数据1, 数据2, 数据3, 数据4 };
数据类型 数组名[][列数] = { 数据1, 数据2, 数据3, 数据4 };
第二种更加直观,提高代码的可读性。在定义二维数组时,如果初始化了数据,可以省略行数。
二维数组数组名的用途:查看二维数组所占内存空间、获取二维数组首地址。
cout << "二维数组大小: " << sizeof(arr) << endl;
cout << "二维数组一行大小: " << sizeof(arr[0]) << endl;
cout << "二维数组元素大小: " << sizeof(arr[0][0]) << endl;
cout << "二维数组行数: " << sizeof(arr) / sizeof(arr[0]) << endl;
cout << "二维数组列数: " << sizeof(arr[0]) / sizeof(arr[0][0]) << endl;
cout << "二维数组首地址:" << arr << endl;
cout << "二维数组第一行地址:" << arr[0] << endl;
cout << "二维数组第二行地址:" << arr[1] << endl;
cout << "二维数组第一个元素地址:" << &arr[0][0] << endl;
cout << "二维数组第二个元素地址:" << &arr[0][1] << endl;
- 二维数组名就是这个数组的首地址
- 对二维数组名进行 sizeof 时,可以获取整个二维数组占用的内存空间大小
函数
概述
将一段经常使用的代码封装起来,减少重复代码。一个较大的程序,一般分为若干个程序块,每个模块实现特定的功能。
主要有 5 个步骤:返回值类型、函数名、参数表列、函数体语句、return 表达式。
返回值类型 函数名(参数列表)
{
函数体语句;
return 表达式;
}
- 返回值类型:一个函数可以返回一个值,在函数定义中
- 函数名:给函数起个名称
- 参数列表:使用该函数时,传入的数据
- 函数体语句:花括号内的代码,函数内需要执行的语句
- return 表达式:和返回值类型挂钩,函数执行完后,返回相应的数据
调用
功能:使用定义好的函数。
语法:函数名(参数)
函数定义里小括号内称为形参,函数调用时传入的参数称为实参。
做的是值传递。所谓值传递,就是函数调用时实参将数值传入给形参。值传递时,如果形参发生改变,并不会影响实参。
样式
有 4 种:
// 无参无返
void test01()
{
// void a = 10; // 无类型不可以创建变量,原因无法分配内存
cout << "this is test01" << endl;
}
// 有参无返
void test02(int a)
{
cout << "this is test02" << endl;
cout << "a = " << a << endl;
}
// 无参有返
int test03()
{
cout << "this is test03 " << endl;
return 10;
}
// 有参有返
int test04(int a, int b)
{
cout << "this is test04 " << endl;
int sum = a + b;
return sum;
}
声明
作用:告诉编译器函数名称及如何调用函数。函数的实际主体可以单独定义。
函数的声明可以多次,但是函数的定义只能有一次。
int max(int a, int b);
int max(int, int);
分文件编写
让代码结构更加清晰。函数分文件编写一般有 4 个步骤:
(1)创建后缀名为 .h 的头文件
(2)创建后缀名为 .cpp 的源文件
(3)在头文件中写函数的声明
(4)在源文件中写函数的定义
swap.h 文件:
#include <iostream>
using namespace std;
// 实现两个数字交换的函数声明
void swap(int a, int b);
swap.cpp 文件:
#include "swap.h"
void swap(int a, int b)
{
int temp = a;
a = b;
b = temp;
cout << "a = " << a << endl;
cout << "b = " << b << endl;
}
main 函数文件:
#include "swap.h"
int main()
{
int a = 100;
int b = 200;
swap(a, b);
system("pause");
return 0;
}
指针
基本概念
作用:可以通过指针间接访问内存。
- 内存编号是从 0 开始记录的,一般用十六进制数字表示
- 可以利用指针变量保存地址
定义和使用
定义语法:数据类型 * 变量名;
int a = 10;
int* p;
p = &a;
cout << &a << endl;
cout << p << endl;
cout << "*p = " << *p << endl;
指针变量和普通变量的区别
普通变量存放的是数据,指针变量存放的是地址。
指针变量可以通过 "*" 操作符操作指针变量指向的内存空间,这个过程称为解引用:
- 通过 & 符号获取变量的地址
- 通过 * 符号解引用获得变量值
- 利用指针可以记录地址
- 对指针变量解引用,可以操作指针指向的内存
指针所占内存空间
指针也是种数据类型,那么这种数据类型占用多少内存空间?
所有指针类型在 32 位操作系统下是 4 个字节(X86),在 64 位操作系统下是 8 个字节(X64)。
空指针
指针变量指向内存中编号为 0 的空间。
用途:初始化指针变量(一开始不知道指向哪合适,就初始化等于 NULL)。
注意:空指针指向的内存是不可以访问的。
// 指针变量 p 指向内存地址编号为 0 的空间
int* p = NULL;
// 访问空指针报错
// 内存编号 0~255 为系统占用内存,不允许用户访问
cout << *p << endl;
野指针
指针变量指向非法的内存空间。
随便给出一个十六进制数,把它强转为地址,用指针变量存放它,理论上是可行的。但这就像是跑到宾馆去开别人的房门,不太道德。为了防止这种不道德的事情发生,编译器会阻止你,告诉你没有权限访问——因为这块内存并不是自己申请来的。
// 指针变量 p 指向内存地址编号为 0x1100 的空间
int* p = (int*)0x1100;
// 访问野指针报错
cout << *p << endl;
空指针和野指针都不是我们申请的空间,因此不要访问。
指针与 const
const 修饰指针有三种情况:const 修饰指针(常量指针)、const 修饰常量(指针常量)、const 既修饰指针又修饰常量。
常量指针 const int* p / int const* p
const 在 * 号前面(修饰 *p 不能改变,但 p 能变):
- 指针的指向可以修改(可以指向其他地址)
- 但不可以通过指针去修改所指向地址里的值
const int* p1 = &a;
int const* p2 = &b;
p1 = &c; // 正确
p2 = &c; // 正确
// *p1 = 100; 报错
// *p2 = 100; 报错
指针常量 int* const p
const 在 * 后面,修饰 p(p 放的是地址,意思是地址不能变):
- 指针指向不可以改,指针指向的值可以更改
- (只能固定指向某一处,不能指向别处,可以通过
*p去修改该地址里的值)
int* const p3 = &a;
// p3 = &b; // 错误
*p3 = 100; // 正确
const 既修饰指针,又修饰常量
既不可以改指向,又不可以通过 *p 去改里面的值。
const int* const p3 = &a;
// p3 = &b; // 错误
// *p3 = 100; // 错误
看 const 右侧紧跟着的是什么、它在 * 的前面还是后面,就知道到底是什么不能改。
constexpr
指针与数组
可以利用指针访问数组中元素。
数组其实就可以看做是一个指针常量 type* const p——一旦定义就只能指向这么一块连续的内存单元;数组名是首地址,相当于指针的名字。指针之间是可以赋值的,用这个指针赋值给另一个指针,使得两个指针都指向同一块地方。利用这一点就能实现用指针访问数组中的每一个元素:
int arr[] = { 1,2,3 };
int *p = arr; // 相当于把一个指针的值赋给另一个指针
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int* p = arr;
cout << "第一个元素: " << arr[0] << endl;
cout << "把数组当指针常量访问第一个元素: " << *arr << endl;
cout << "指针访问第一个元素: " << *p << endl;
for (int i = 0; i < 10; i++)
{
cout << arr[i] << endl;
}
for (int i = 0; i < 10; i++)
{
cout << *p++ << endl; // 利用指针遍历数组
}
但是不能 arr++,因为它只能指向数组的头。其他方面两者都可以换来换去用,是一样的。
指针和函数
利用指针作函数参数,可以修改实参的值——值传递和地址传递的区别:
原本函数里只是传值,里面的修改对外面没有影响,要做改变得靠返回值。但传地址就不一样了,直接用门牌号让函数直接拿外面的实参进行操作,函数里的操作就是对外面数据本身的操作,因此可以实现修改的功能。
// 值传递
void swap1(int a, int b)
{
int temp = a;
a = b;
b = temp;
}
// 地址传递
void swap2(int* p1, int* p2)
{
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
int main()
{
int a = 10;
int b = 20;
swap1(a, b); // 值传递不会改变实参
swap2(&a, &b); // 地址传递会改变实参
cout << "a = " << a << endl;
cout << "b = " << b << endl;
system("pause");
return 0;
}
如果不想修改实参,就用值传递;如果想修改实参,就用地址传递。
指针、数组、函数
// 冒泡排序函数
void bubbleSort(int* arr, int len) // int * arr 也可以写为 int arr[]
{
for (int i = 0; i < len - 1; i++)
{
for (int j = 0; j < len - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
// 打印数组函数
void printArray(int arr[], int len)
{
for (int i = 0; i < len; i++)
{
cout << arr[i] << endl;
}
}
int main() {
int arr[10] = { 4,3,6,9,1,2,10,8,7,5 };
int len = sizeof(arr) / sizeof(int);
bubbleSort(arr, len);
printArray(arr, len);
system("pause");
return 0;
}
当数组名传入到函数作为参数时,被退化为指向首元素的指针。
结构
概念
结构体属于用户自定义的数据类型,允许用户存储不同的数据类型。
定义/使用
struct 结构体名
{
结构体成员列表
};
struct student
{
string name;
int age;
int score;
};
通过结构体创建变量的方式有三种:
(1) 定义结构体时顺便创建变量:
struct student
{
string name;
int age;
int score;
}stu;
(2) struct 结构体名 变量名:
struct student stu;
stu.name = "张三";
stu.age = 18;
stu.score = 100;
(3) struct 结构体名 变量名 = { 成员1值, 成员2值... }:
struct student stu = { "李四",19,60 };
输出:
cout << "姓名:" << stu.name << " 年龄:" << stu.age << " 分数:" << stu.score << endl;
在 C++ 中:
- 定义结构体时的关键字是 struct,不可省略
- 创建结构体变量时,关键字 struct 可以省略(C 中不能省)
- 结构体变量利用操作符 "." 访问成员
结构体数组
将自定义的结构体放入到数组中方便维护。
语法:
struct 结构体名 数组名[元素个数] = { {} , {} , ... {} };
struct student
{
string name; // 姓名
int age; // 年龄
int score; // 分数
};
// main 里
struct student arr[3] =
{
{"张三",18,80 },
{"李四",19,60 },
{"王五",20,70 }
};
for (int i = 0; i < 3; i++)
cout << "姓名:" << arr[i].name << " 年龄:" << arr[i].age << " 分数:" << arr[i].score << endl;
相较于 C,C++ 里输出要简单一些。不过输出字符串要头文件 <string>。
结构体指针
作用:通过指针访问结构体中的成员。
利用操作符 -> 可以通过结构体指针访问结构体属性。
struct student
{
string name;
int age;
int score;
};
main 中:
student stu = { "张三",18,100 };
student* p = &stu;
p->score = 80; // 指针通过 -> 操作符可以访问成员
cout << "姓名:" << p->name << " 年龄:" << p->age << " 分数:" << p->score << endl;
结构体嵌套结构体
结构体中的成员可以是另一个结构体。
// 学生结构体定义
struct student
{
string name; // 姓名
int age; // 年龄
int score; // 分数
};
// 教师结构体定义
struct teacher
{
int id; // 职工编号
string name; // 教师姓名
int age; // 教师年龄
struct student stu; // 子结构体 学生
};
结构体做函数参数
作用:将结构体作为参数向函数中传递。
传递方式有两种。
值传递:
void printStudent(student stu)
{
stu.age = 28;
cout << "子函数中 姓名:" << stu.name << " 年龄: " << stu.age << " 分数:" << stu.score << endl;
}
// main 中
student stu = { "张三",18,100 };
printStudent(stu);
cout << "主函数中 姓名:" << stu.name << " 年龄: " << stu.age << " 分数:" << stu.score << endl;
地址传递:
void printStudent2(student* stu)
{
stu->age = 28;
cout << "子函数中 姓名:" << stu->name << " 年龄: " << stu->age << " 分数:" << stu->score << endl;
}
printStudent2(&stu);
cout << "主函数中 姓名:" << stu.name << " 年龄: " << stu.age << " 分数:" << stu.score << endl;
同理,一个不被修改、一个被修改。如果不想修改主函数中的数据,用值传递,反之用地址传递。通常情况下是希望改变的,而如果传值就需要进行复制操作,浪费空间和时间,所以不如传指针来得快。
结构体中的 const
用 const 来防止误操作。像写输出函数时,保证不会修改它的值,就能在函数参数表中加上 const。
void printStudent(const student* stu)
{
// stu->age = 100; // 操作失败,因为加了const修饰
cout << "姓名:" << stu->name << " 年龄:" << stu->age << " 分数:" << stu->score << endl;
}
const 在参数表时,表示它修饰的东西在该函数内不可被修改。
通讯录管理系统
四个版本的通讯录工程(本机路径存档):
D:\VSC++代码\通讯录管理系统\通讯录管理系统\通讯录管理系统.cpp
D:\VSC++代码\通讯录链表实现\通讯录链表实现\源.cpp
D:\VSC++代码\通讯录_链表_mysql\通讯录_链表_mysql\通讯录_链表_mysql.cpp
D:\EasyX代码\图形化通讯录\图形化通讯录\源.cpp
C11 新特性
别名
typedef 和 using。
重点:P61。
要理解"推出",不能直接代入。由 typedef char* pstring; 得 pstring 为 char 的指针。
要把它作为一个整体:const pstring cstr = 0 是 char* const cstr。
意义是 cstr 为常量指针,它的指向不能改。如果直接代入去看,成了 const char* cstr,那么意义就变成了指向字符常量的指针,意义扭曲了。
so 重点在于:要当做整体,而非代入。
未知(需推)类型
auto
decltype
int i = 42, *p = &i;
decltype(*p) 的推断结果为 int&。注意:解引用得到的是指针所指的对象,并可以对其进行操作,这很符合引用的作用,所以对于得到 int& 的结果可以理解。如果反推的话,若得到的结果是个 int,那就是传值而非指针了,操作不相关了,所以不应该是 int。
另一个问题:decltype((val)) 这种双重括号得到的一定是引用,而 decltype(val) 单重括号得到的是不是引用得根据 val 自身决定。
为什么双重括号一定是引用?把括号内的东西当做表达式去理解:
??理解成变量其实也就是对一块地址取名字,是对该地址的引用?所以这个表达式的结果是引用?以及赋值是会产生引用的一类经典表达式,引用的类型就是左值的类型,这个是否也是这样理解?所以其实变量的一切都是引用,引用的某个内存空间,然后对于变量的引用又是一个引用……???
这一切都和左值相关:左值返回的是引用类型。双重括号 (variable) 将变量视为一个表达式,这个表达式的结果类型是 variable 的类型作为引用——因为双重括号强制将 variable 视为一个左值表达式,而左值表达式的 decltype 结果是引用类型,以便可以修改它们的值。
同样适用于"赋值是会产生引用的一类经典表达式,引用的类型就是左值的类型":赋值表达式的结果就是左操作数本身(左值),其引用类型与左值的类型相同——赋值操作需要将右边的值赋给左边的变量,左值可以被修改,因此结果是一个引用以允许对左值进行修改。
所以可以这样:
int a = 3, b = 4;
decltype((a = b)) result = a; // result 是 int& 类型
对照三种推导:
int a = 3, b = 4;
decltype(a) x = a; // x 的类型是 int(a 是变量名,取声明类型)
decltype(a = b) y = a; // y 的类型是 int&(赋值表达式的结果就是左操作数,是左值)
decltype((a = b)) z = a; // z 的类型是 int&(加上括号仍是同一个左值)
总之,decltype 的推导结果取决于表达式的左值或右值属性:变量名(不带括号)取声明类型;带括号或运算的表达式按值类别推导,左值得引用、右值得类型本身。
关于头文件 name.h or cname
C 中的头文件在 C++ 中兼容。
附件
📄 C++.docx(原 word 笔记):C++.docx
💬 评论